﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  public class SelectionRange
  {
    #region Constructors

    public SelectionRange( object item )
      : this()
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      this.StartRangeInfos.SetTargetItem( item );
      this.EndRangeInfos.SetTargetItem( item );

      m_isSingleItem = true;
    }

    public SelectionRange( FilterExpressionBase filterExpression, Predicate<object> filter )
      : this()
    {
      if( ( filterExpression == null ) && ( filter == null ) )
        throw new InvalidOperationException( "An attempt was made to create a selection range while both the filterExpression and filter parameters are null." );

      m_filterExpression = filterExpression;
      m_filter = filter;
      m_useDefaultDataRangeContext = false;
    }

    public SelectionRange( IEnumerable<SortDescription> sortDescriptons, FilterExpressionBase filterExpression, Predicate<object> filter )
      : this()
    {
      if( sortDescriptons == null )
        throw new ArgumentNullException( "sortDescriptons" );

      m_sortDescriptions.AddRange( sortDescriptons );
      m_filterExpression = filterExpression;
      m_filter = filter;
      m_useDefaultDataRangeContext = false;
    }

    internal SelectionRange()
    {
      m_startRange = new StartRangeDictionary( this );
      m_endRange = new EndRangeDictionary( this );

      this.SortDescriptions = new ReadOnlyCollection<SortDescription>( m_sortDescriptions );
    }

    internal SelectionRange( DataPath item )
      : this()
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      this.StartRangeInfos.SetTargetPath( item );
      this.EndRangeInfos.SetTargetPath( item );

      m_isSingleItem = true;
    }

    internal SelectionRange( SelectionRange selectionRange )
      : this()
    {
      if( selectionRange == null )
        throw new ArgumentNullException( "selectionRange" );

      m_sortDescriptions.AddRange( selectionRange.SortDescriptions );
      m_filterExpression = selectionRange.FilterExpression;
      m_filter = selectionRange.Filter;
      m_useDefaultDataRangeContext = selectionRange.UseDefaultDataRangeContext;
      m_selectionType = selectionRange.SelectionType;
      m_isSingleItem = selectionRange.m_isSingleItem;

      if( !selectionRange.SelectFromStart )
      {
        StartRangeDictionary rangeInfos = selectionRange.StartRangeInfos;

        this.StartRangeInfos.SetTargetPath( rangeInfos.TargetPath );
        this.StartRangeInfos.SetTargetItem( rangeInfos.TargetItem );

        if( ( rangeInfos.TargetPath == null ) && ( rangeInfos.TargetItem == null ) )
        {
          foreach( KeyValuePair<string, object> entry in rangeInfos )
          {
            this.StartRangeInfos.Add( entry );
          }
        }
      }

      if( !selectionRange.SelectToEnd )
      {
        EndRangeDictionary rangeInfos = selectionRange.EndRangeInfos;

        this.EndRangeInfos.SetTargetPath( rangeInfos.TargetPath );
        this.EndRangeInfos.SetTargetItem( rangeInfos.TargetItem );

        if( ( rangeInfos.TargetPath == null ) && ( rangeInfos.TargetItem == null ) )
        {
          foreach( KeyValuePair<string, object> entry in rangeInfos )
          {
            this.EndRangeInfos.Add( entry );
          }
        }
      }
    }

    #endregion

    #region All Property

    public static SelectionRange All
    {
      get
      {
        return new SelectionRange();
      }
    }

    #endregion

    #region SortDescriptions Property

    public IList<SortDescription> SortDescriptions
    {
      get;
      private set;
    }

    private readonly List<SortDescription> m_sortDescriptions = new List<SortDescription>( 0 );

    #endregion

    #region SelectionType Property

    public SelectionType SelectionType
    {
      get
      {
        return m_selectionType;
      }
      set
      {
        this.CheckFrozen();

        m_selectionType = value;
      }
    }

    private SelectionType m_selectionType = SelectionType.Selection;

    #endregion

    #region FilterExpression Property

    public FilterExpressionBase FilterExpression
    {
      get
      {
        return m_filterExpression;
      }
    }

    private FilterExpressionBase m_filterExpression; //null
    private Func<object, bool> m_filterExpressionPredicate; //null

    #endregion

    #region Filter Property

    public Predicate<object> Filter
    {
      get
      {
        return m_filter;
      }
    }

    private Predicate<object> m_filter;

    #endregion

    #region StartRangeInfos Property

    public StartRangeDictionary StartRangeInfos
    {
      get
      {
        return m_startRange;
      }
    }

    private readonly StartRangeDictionary m_startRange;

    #endregion

    #region EndRangeInfos Property

    public EndRangeDictionary EndRangeInfos
    {
      get
      {
        return m_endRange;
      }
    }

    private readonly EndRangeDictionary m_endRange;

    #endregion

    #region IsEmpty Property

    public bool IsEmpty
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region IsSingleItem Internal Property

    internal bool IsSingleItem
    {
      get
      {
        return ( m_isSingleItem )
            && ( this.StartRangeInfos.TargetItem == this.EndRangeInfos.TargetItem )
            && ( this.StartRangeInfos.TargetPath == this.EndRangeInfos.TargetPath );
      }
    }

    private bool m_isSingleItem; //false

    #endregion

    #region UseDefaultDataRangeContext Internal Property

    internal bool UseDefaultDataRangeContext
    {
      get
      {
        return m_useDefaultDataRangeContext;
      }
    }

    private readonly bool m_useDefaultDataRangeContext = true;

    #endregion

    #region SelectFromStart Internal Property

    internal bool SelectFromStart
    {
      get
      {
        return m_startRange.SelectFromStart;
      }
    }

    #endregion

    #region SelectToEnd Internal Property

    internal bool SelectToEnd
    {
      get
      {
        return m_endRange.SelectToEnd;
      }
    }

    #endregion

    #region DataRangeContext Internal Property

    internal DataRangeContext DataRangeContext
    {
      get
      {
        return m_dataRangeContext;
      }
    }

    private DataRangeContext m_dataRangeContext;

    #endregion

    #region IsStartRangeBeforeEndRange Private Property

    private bool IsStartRangeBeforeEndRange
    {
      get
      {
        if( !m_isStartRangeBeforeEndRange.HasValue )
        {
          if( ( this.StartRangeInfos.SelectFromStart )
            || ( this.EndRangeInfos.SelectToEnd )
            || ( this.IsSingleItem ) )
          {
            m_isStartRangeBeforeEndRange = true;
          }
          else
          {
            m_isStartRangeBeforeEndRange = ( DataRange.CompareHelper(
                                               this.StartRangeInfos,
                                               this.StartRangeInfos.IsInclusive,
                                               this.EndRangeInfos,
                                               this.EndRangeInfos.IsInclusive,
                                               this.SortDescriptions ) <= 0 );
          }
        }

        return m_isStartRangeBeforeEndRange.Value;
      }
    }

    private Nullable<bool> m_isStartRangeBeforeEndRange; //null

    #endregion

    #region IsLowerBoundChildOfUpperBound Private Property

    private bool IsLowerBoundChildOfUpperBound
    {
      get
      {
        if( !m_isLowerBoundChildOfUpperBound.HasValue )
        {
          this.SetBoundsRelation();
        }

        return m_isLowerBoundChildOfUpperBound.Value;
      }
    }

    private Nullable<bool> m_isLowerBoundChildOfUpperBound; //null

    #endregion

    #region IsUpperBoundChildOfLowerBound Private Property

    private bool IsUpperBoundChildOfLowerBound
    {
      get
      {
        if( !m_isUpperBoundChildOfLowerBound.HasValue )
        {
          this.SetBoundsRelation();
        }

        return m_isUpperBoundChildOfLowerBound.Value;
      }
    }

    private Nullable<bool> m_isUpperBoundChildOfLowerBound; //null

    #endregion

    #region IsFrozen Private Property

    private bool IsFrozen
    {
      get
      {
        return m_frozen;
      }
    }

    private bool m_frozen; //false

    #endregion

    public override bool Equals( object obj )
    {
      return this.Equals( obj as SelectionRange );
    }

    public bool Equals( SelectionRange obj )
    {
      if( obj == null )
        return false;

      if( obj == this )
        return true;

      if( ( this.SelectionType != obj.SelectionType )
        || ( !object.Equals( this.Filter, obj.Filter ) )
        || ( !object.Equals( this.FilterExpression, obj.FilterExpression ) )
        || ( this.SelectFromStart != obj.SelectFromStart )
        || ( this.SelectToEnd != obj.SelectToEnd )
        || ( this.StartRangeInfos.IsInclusive != obj.StartRangeInfos.IsInclusive )
        || ( this.EndRangeInfos.IsInclusive != obj.EndRangeInfos.IsInclusive )
        || ( this.StartRangeInfos.TargetPath != obj.StartRangeInfos.TargetPath )
        || ( this.EndRangeInfos.TargetPath != obj.EndRangeInfos.TargetPath )
        || ( this.StartRangeInfos.TargetItem != obj.StartRangeInfos.TargetItem )
        || ( this.EndRangeInfos.TargetItem != obj.EndRangeInfos.TargetItem )
        || ( !SelectionRange.Equals( this.StartRangeInfos, obj.StartRangeInfos ) )
        || ( !SelectionRange.Equals( this.EndRangeInfos, obj.EndRangeInfos ) ) )
        return false;

      // Select all or select at most a single item.
      if( ( ( this.SelectFromStart ) && ( this.SelectToEnd ) )
        || ( this.IsSingleItem ) )
        return true;

      return this.SortDescriptions.SequenceEqual( obj.SortDescriptions );
    }

    public override int GetHashCode()
    {
      int hash = 17;

      foreach( SortDescription sortDesc in this.SortDescriptions )
      {
        hash = hash ^ sortDesc.GetHashCode();

        object outValue;

        if( !this.StartRangeInfos.TryGetValue( sortDesc.PropertyName, out outValue ) )
        {
          throw new InvalidOperationException( "The data in the SelectionRange is not consistent." );
        }
        else
        {
          hash = hash ^ outValue.GetHashCode();
        }

        if( !this.EndRangeInfos.TryGetValue( sortDesc.PropertyName, out outValue ) )
        {
          throw new InvalidOperationException( "The data in the SelectionRange is not consistent." );
        }
        else
        {
          hash = hash ^ outValue.GetHashCode();
        }
      }

      return hash;
    }

    internal void Freeze( 
      DataRangeContext context, 
      IDataSourceWrapperHelper dataHelper,
      bool isDataSourcePrimaryKeyVolatile )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( dataHelper == null )
        throw new ArgumentNullException( "dataHelper" );

      this.CheckFrozen();

      m_dataRangeContext = context;
      m_dataHelper = dataHelper;

      Debug.Assert( ( m_filter == null ) || ( m_filter == m_dataRangeContext.Filter ), "Unexpected filter." );
      m_filter = m_dataRangeContext.Filter;

      Debug.Assert( ( m_filterExpression == null ) || ( m_filterExpression == m_dataRangeContext.FilterExpression ), "Unexpected FilterExpression." );
      m_filterExpression = m_dataRangeContext.FilterExpression;

      m_sortDescriptions.Clear();
      m_sortDescriptions.AddRange( m_dataRangeContext.SortDescriptions );

      this.FillKeyValuesFromItems( dataHelper, isDataSourcePrimaryKeyVolatile );

      m_frozen = true;
    }

    internal bool Contains( DataPath dataPath )
    {
      return ( dataPath != null )
          && ( this.IsAfterLowerBound( dataPath ) )
          && ( this.IsBeforeUpperBound( dataPath ) )
          && ( !this.IsFilteredOut( dataPath ) );
    }

    internal IDictionary<string, object> GetLowerBound( out bool isInclusive )
    {
      IDictionary<string, object> lowerBound;
      bool isLowerBoundInclusive;
      IDictionary<string, object> upperBound;
      bool isUpperBoundInclusive;

      this.GetBounds( out lowerBound, out isLowerBoundInclusive, out upperBound, out isUpperBoundInclusive );

      isInclusive = isLowerBoundInclusive;

      return lowerBound;
    }

    internal IDictionary<string, object> GetUpperBound( out bool isInclusive )
    {
      IDictionary<string, object> lowerBound;
      bool isLowerBoundInclusive;
      IDictionary<string, object> upperBound;
      bool isUpperBoundInclusive;

      this.GetBounds( out lowerBound, out isLowerBoundInclusive, out upperBound, out isUpperBoundInclusive );

      isInclusive = isUpperBoundInclusive;

      return upperBound;
    }

    private static bool Equals( IDictionary<string, object> x, IDictionary<string, object> y )
    {
      if( x.Count != y.Count )
        return false;

      foreach( KeyValuePair<string, object> entry in x )
      {
        object value;

        if( !y.TryGetValue( entry.Key, out value ) )
          return false;

        if( !object.Equals( entry.Value, value ) )
          return false;
      }

      return true;
    }

    private static int Compare( object a, object b, ListSortDirection direction )
    {
      int result;

      if( a == b )
      {
        result = 0;
      }
      else if( a == null )
      {
        result = -1;
      }
      else if( b == null )
      {
        result = 1;
      }
      else
      {
        IComparable comparable = a as IComparable;

        if( comparable == null )
          throw new InvalidOperationException( "An attempt was made to compare objects that do not implement IComparable." );

        result = comparable.CompareTo( b );
      }

      if( direction == ListSortDirection.Ascending )
        return result;

      return -result;
    }

    private static void FillKeyValuesFromItem(
      IDataSourceWrapperHelper dataHelper,
      DataPath dataPath,
      IEnumerable<SortDescription> sortOrder,
      IDictionary<string, object> storage )
    {
      foreach( SortDescription sort in sortOrder )
      {
        string propertyPath = sort.PropertyName;
        object value = dataHelper.GetMemberValue( propertyPath, dataPath );

        storage.Add( propertyPath, value );
      }
    }

    private void FillKeyValuesFromItems( 
      IDataSourceWrapperHelper dataHelper, 
      bool isDataSourcePrimaryKeyVolatile )
    {
      if( !this.SelectFromStart )
      {
        DataPath dataPath = this.StartRangeInfos.TargetPath;
        object item = this.StartRangeInfos.TargetItem;

        if( ( dataPath == null ) && ( item != null ) )
        {
          if( isDataSourcePrimaryKeyVolatile )
            throw new NotSupportedException( "An attempt was made to add a selection range that defines an item as the start or end range infos to a data source whose primary keys may have changed." );

          dataPath = dataHelper.Convert( item );

          if( dataPath == null )
            throw new NotSupportedException( "Unusable starting item." );

          this.StartRangeInfos.SetTargetPath( dataPath );
        }
        else if( ( dataPath != null ) && ( item == null ) )
        {
          item = dataHelper.ConvertBack( dataPath );

          if( item == null )
            throw new NotSupportedException( "Unusable starting item." );

          this.StartRangeInfos.SetTargetItem( item );
        }

        if( dataPath != null )
        {
          SelectionRange.FillKeyValuesFromItem( dataHelper, dataPath, this.SortDescriptions, this.StartRangeInfos );
        }
      }

      if( !this.SelectToEnd )
      {
        DataPath dataPath = this.EndRangeInfos.TargetPath;
        object item = this.EndRangeInfos.TargetItem;

        if( ( dataPath == null ) && ( item != null ) )
        {
          if( isDataSourcePrimaryKeyVolatile )
            throw new InvalidOperationException( "An attempt was made to add a selection range that defines an item as the start or end range infos to a data source whose primary keys may have changed." );

          dataPath = dataHelper.Convert( item );

          if( dataPath == null )
            throw new NotSupportedException( "Unusable ending item." );

          this.EndRangeInfos.SetTargetPath( dataPath );
        }
        else if( ( dataPath != null ) && ( item == null ) )
        {
          item = dataHelper.ConvertBack( dataPath );

          if( item == null )
            throw new NotSupportedException( "Unusable ending item." );

          this.EndRangeInfos.SetTargetItem( item );
        }

        if( dataPath != null )
        {
          SelectionRange.FillKeyValuesFromItem( dataHelper, dataPath, this.SortDescriptions, this.EndRangeInfos );
        }
      }
    }

    private void GetBounds(
      out IDictionary<string, object> lowerBound,
      out bool isLowerBoundInclusive,
      out IDictionary<string, object> upperBound,
      out bool isUpperBoundInclusive )
    {
      if( this.IsStartRangeBeforeEndRange )
      {
        lowerBound = this.StartRangeInfos;
        isLowerBoundInclusive = this.StartRangeInfos.IsInclusive;
        upperBound = this.EndRangeInfos;
        isUpperBoundInclusive = this.EndRangeInfos.IsInclusive;
      }
      else
      {
        lowerBound = this.EndRangeInfos;
        isLowerBoundInclusive = this.EndRangeInfos.IsInclusive;
        upperBound = this.StartRangeInfos;
        isUpperBoundInclusive = this.StartRangeInfos.IsInclusive;
      }
    }

    private void GetBounds(
      out IDictionary<string, object> lowerBound,
      out IDictionary<string, object> upperBound )
    {
      bool isLowerBoundInclusive;
      bool isUpperBoundInclusive;

      this.GetBounds( out lowerBound, out isLowerBoundInclusive, out upperBound, out isUpperBoundInclusive );
    }

    private void SetBoundsRelation()
    {
      m_isLowerBoundChildOfUpperBound = false;
      m_isUpperBoundChildOfLowerBound = false;

      IDictionary<string, object> lowerBound;
      IDictionary<string, object> upperBound;

      this.GetBounds( out lowerBound, out upperBound );

      foreach( SortDescription sortOrder in this.SortDescriptions )
      {
        string propertyPath = sortOrder.PropertyName;

        object lowerValue;
        bool lowerValueFound = lowerBound.TryGetValue( propertyPath, out lowerValue );

        object upperValue;
        bool upperValueFound = upperBound.TryGetValue( propertyPath, out upperValue );

        if( lowerValueFound && upperValueFound )
        {
          bool sameValues = object.Equals( lowerValue, upperValue );

          m_isLowerBoundChildOfUpperBound = sameValues;
          m_isUpperBoundChildOfLowerBound = sameValues;

          if( !sameValues )
            break;
        }
        else
        {
          m_isLowerBoundChildOfUpperBound = ( lowerValueFound && !upperValueFound );
          m_isUpperBoundChildOfLowerBound = ( !lowerValueFound && upperValueFound );

          break;
        }
      }
    }

    private bool IsAfterLowerBound( DataPath dataPath )
    {
      if( this.SelectFromStart )
        return true;

      bool isInclusive;
      IDictionary<string, object> bound = this.GetLowerBound( out isInclusive );
      bool isParentBound = this.IsUpperBoundChildOfLowerBound;

      foreach( SortDescription sortOrder in this.SortDescriptions )
      {
        string propertyPath = sortOrder.PropertyName;
        object boundValue;

        if( !bound.TryGetValue( propertyPath, out boundValue ) )
          return ( isParentBound || isInclusive );

        object itemValue = m_dataHelper.GetMemberValue( propertyPath, dataPath );
        int compare = SelectionRange.Compare( itemValue, boundValue, sortOrder.Direction );

        if( compare != 0 )
          return ( compare > 0 );
      }

      return isInclusive;
    }

    private bool IsBeforeUpperBound( DataPath dataPath )
    {
      if( this.SelectToEnd )
        return true;

      bool isInclusive;
      IDictionary<string, object> bound = this.GetUpperBound( out isInclusive );
      bool isParentBound = this.IsLowerBoundChildOfUpperBound;

      foreach( SortDescription sortOrder in this.SortDescriptions )
      {
        string propertyPath = sortOrder.PropertyName;
        object boundValue;

        if( !bound.TryGetValue( propertyPath, out boundValue ) )
          return ( isParentBound || isInclusive );

        object itemValue = m_dataHelper.GetMemberValue( propertyPath, dataPath );
        int compare = SelectionRange.Compare( itemValue, boundValue, sortOrder.Direction );

        if( compare != 0 )
          return ( compare < 0 );
      }

      return isInclusive;
    }

    private bool IsFilteredOut( DataPath dataPath )
    {
      if( dataPath == null )
        return true;

      object item = m_dataHelper.ConvertBack( dataPath );
      if( item == null )
        return true;

      if( this.Filter != null && !this.Filter( item ) )
        return true;

      if( this.FilterExpression != null )
      {
        if( m_filterExpressionPredicate == null )
        {
          PredicateBuilder pb = new PredicateBuilder( this.FilterExpression );

          m_filterExpressionPredicate = pb.ToPredicate( item.GetType() );
        }

        if( !m_filterExpressionPredicate.Invoke( item ) )
          return true;
      }

      return false;
    }

    private void CheckFrozen()
    {
      if( this.IsFrozen )
        throw new InvalidOperationException( "The Selection is read only." );
    }

    #region Private Fields

    private IDataSourceWrapperHelper m_dataHelper; //null

    #endregion

    #region StartRangeDictionary Nested Type

    public sealed class StartRangeDictionary : NotificationDictionary<string, object>
    {
      internal StartRangeDictionary( SelectionRange selectionRange )
        : base( selectionRange )
      {
      }

      public bool IsInclusive
      {
        get
        {
          return m_isInclusive;
        }
        set
        {
          this.CheckReadOnly();

          m_isInclusive = value;
        }
      }

      internal bool SelectFromStart
      {
        get
        {
          return ( this.TargetPath == null )
              && ( this.TargetItem == null )
              && ( this.Count == 0 );
        }
      }

      public void FromStart()
      {
        this.Clear();
      }

      public void FromItem( object item )
      {
        this.Clear();
        this.SetTargetItem( item );
      }

      internal void FromPath( DataPath path )
      {
        this.Clear();
        this.SetTargetPath( path );
      }

      private bool m_isInclusive = true;
    }

    #endregion

    #region EndRangeDictionary Nested Type

    public sealed class EndRangeDictionary : NotificationDictionary<string, object>
    {
      internal EndRangeDictionary( SelectionRange selectionRange )
        : base( selectionRange )
      {
      }

      public bool IsInclusive
      {
        get
        {
          return m_isInclusive;
        }
        set
        {
          this.CheckReadOnly();

          m_isInclusive = value;
        }
      }

      internal bool SelectToEnd
      {
        get
        {
          return ( this.TargetPath == null )
              && ( this.TargetItem == null )
              && ( this.Count == 0 );
        }
      }

      public void ToEnd()
      {
        this.Clear();
      }

      public void ToItem( object item )
      {
        this.Clear();
        this.SetTargetItem( item );
      }

      internal void ToPath( DataPath path )
      {
        this.Clear();
        this.SetTargetPath( path );
      }

      private bool m_isInclusive = true;
    }

    #endregion

    #region NotificationDictionary Nested Type

    public abstract class NotificationDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
      internal NotificationDictionary( SelectionRange selectionRange )
      {
        m_selectionRange = selectionRange;
      }

      internal object TargetItem
      {
        get
        {
          return m_targetItem;
        }
      }

      internal DataPath TargetPath
      {
        get
        {
          return m_targetPath;
        }
      }

      public event EventHandler DictionaryChanging;
      public event EventHandler DictionaryChanged;

      #region IDictionary<TKey,TValue> Members

      public void Add( TKey key, TValue value )
      {
        this.CheckReadOnly();

        this.OnDictionaryChanging();
        m_wrappedDictionary.Add( key, value );
        this.OnDictionaryChanged();
      }

      public bool ContainsKey( TKey key )
      {
        return m_wrappedDictionary.ContainsKey( key );
      }

      public ICollection<TKey> Keys
      {
        get
        {
          return m_wrappedDictionary.Keys;
        }
      }

      public bool Remove( TKey key )
      {
        this.CheckReadOnly();

        this.OnDictionaryChanging();
        bool retval = m_wrappedDictionary.Remove( key );
        this.OnDictionaryChanged();

        return retval;
      }

      public bool TryGetValue( TKey key, out TValue value )
      {
        return m_wrappedDictionary.TryGetValue( key, out value );
      }

      public ICollection<TValue> Values
      {
        get
        {
          return m_wrappedDictionary.Values;
        }
      }

      public TValue this[ TKey key ]
      {
        get
        {
          return m_wrappedDictionary[ key ];
        }
        set
        {
          this.CheckReadOnly();

          this.OnDictionaryChanging();
          m_wrappedDictionary[ key ] = value;
          this.OnDictionaryChanged();
        }
      }

      #endregion

      #region ICollection<KeyValuePair<TKey,TValue>> Members

      public void Add( KeyValuePair<TKey, TValue> item )
      {
        this.CheckReadOnly();

        this.OnDictionaryChanging();
        ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).Add( item );
        this.OnDictionaryChanged();
      }

      public void Clear()
      {
        this.CheckReadOnly();

        this.OnDictionaryChanging();

        m_wrappedDictionary.Clear();
        m_targetItem = null;
        m_targetPath = null;
        m_selectionRange.m_isSingleItem = false;

        this.OnDictionaryChanged();
      }

      public bool Contains( KeyValuePair<TKey, TValue> item )
      {
        return ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).Contains( item );
      }

      public void CopyTo( KeyValuePair<TKey, TValue>[] array, int arrayIndex )
      {
        ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).CopyTo( array, arrayIndex );
      }

      public int Count
      {
        get
        {
          return m_wrappedDictionary.Count;
        }
      }

      public bool IsReadOnly
      {
        get
        {
          return ( m_selectionRange.IsFrozen )
              || ( ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).IsReadOnly );
        }
      }

      public bool Remove( KeyValuePair<TKey, TValue> item )
      {
        this.CheckReadOnly();

        this.OnDictionaryChanging();
        bool retval = ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).Remove( item );
        this.OnDictionaryChanged();

        return retval;
      }

      #endregion

      #region IEnumerable<KeyValuePair<TKey,TValue>> Members

      public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
      {
        return ( ( IEnumerable<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).GetEnumerator();
      }

      #endregion

      #region IEnumerable Members

      IEnumerator IEnumerable.GetEnumerator()
      {
        return ( ( IEnumerable )m_wrappedDictionary ).GetEnumerator();
      }

      #endregion

      protected void OnDictionaryChanging()
      {
        if( this.DictionaryChanging != null )
        {
          this.DictionaryChanging( this, EventArgs.Empty );
        }
      }

      protected void OnDictionaryChanged()
      {
        if( this.DictionaryChanged != null )
        {
          this.DictionaryChanged( this, EventArgs.Empty );
        }
      }

      protected void CheckReadOnly()
      {
        if( this.IsReadOnly )
          throw new InvalidOperationException( "The collection is read only." );
      }

      internal void SetTargetItem( object item )
      {
        m_targetItem = item;
      }

      internal void SetTargetPath( DataPath path )
      {
        m_targetPath = path;
      }

      private Dictionary<TKey, TValue> m_wrappedDictionary = new Dictionary<TKey, TValue>();
      private SelectionRange m_selectionRange;
      private DataPath m_targetPath; //null
      private object m_targetItem; //null
    }

    #endregion
  }
}
